/*
 * KernelBasisComponent.java
 *
 * Created on April 27, 2005, 2:17 PM
 */

package com.milowski.monos.component;

import java.util.*;
import java.io.*;

import com.milowski.monos.*;
import org.infoset.component.Component;
import org.infoset.component.ItemFilterComponent;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.filter.SubtreeFilter;

/**
 *
 * @author R. Alexander Milowski
 */
public class SolveLPComponent extends MonosComponentBase
{
   
   static final Name componentName = InfosetFactory.createName(XML.STEPS_NAMESPACE,"solve-lp");
   static Name [] subtreeNames = { XML.linearProgramName };

   public static class Rational {
      int numerator;
      int denominator;
      public Rational(int numerator,int denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
      }
      
      public int getNumerator() {
         return numerator;
      }
      
      public int getDenominator() {
         return denominator;
      }
      
      public String toString() {
         return numerator==0 ? "0" : ((denominator==1) ? Integer.toString(numerator) : Integer.toString(numerator)+'/'+Integer.toString(denominator));
      }
      
      public static Rational parseRational(String spec)
         throws NumberFormatException
      {
         int slash = spec.indexOf('/');
         if (slash<0) {
            return new Rational(Integer.parseInt(spec),1);
         } else {
            return new Rational(Integer.parseInt(spec.substring(0, slash)),Integer.parseInt(spec.substring(slash+1)));
         }
      }
      
   }
   
   public static class Solution {
      Rational [] point;
      Rational value;
      
      public Solution(Rational [] point,Rational value) {
         this.point = point;
         this.value = value;
      }
      
      public Rational [] getPoint() {
         return point;
      }
      
      public Rational getValue() {
         return value;
      }
      
   }
   
   public interface LPSolver {
      Solution solve(Reader input)
         throws IOException;
   }
   
   public static class CDDRPlusLPSolver implements LPSolver {
      static String program;
      static {
         program = System.getProperty("cddr");
         if (program==null) {
            program = "cddr+";
         }
      }
      public Solution solve(Reader input) 
         throws IOException
      {
         File tmpfile = File.createTempFile("lpcomponent",".ine");
         FileWriter out = new FileWriter(tmpfile);
         char [] buffer = new char[1024];
         int len;
         while ((len = input.read(buffer))>=0) {
            out.write(buffer,0,len);
         }
         out.close();
         
         String [] cmd = new String[2];
         cmd[0] = program;
         cmd[1] = tmpfile.getAbsolutePath();
         
         Process proc = Runtime.getRuntime().exec(cmd);
         
         InputStream proginput = proc.getInputStream();
         BufferedReader breader   = new BufferedReader(new InputStreamReader(proginput));
         
         String line;
         while ((line = breader.readLine())!=null && !line.equals("begin"));

         Solution solution = null;
         if (line!=null) {
            while ((line = breader.readLine())!=null && !line.startsWith("  primal_solution"));
            List pointValues = new ArrayList();
            while ((line = breader.readLine())!=null && !line.startsWith("  dual_solution")) {
               int colon = line.indexOf(':');
               pointValues.add(Rational.parseRational(line.substring(colon+1).trim()));
            }
            while ((line = breader.readLine())!=null && !line.startsWith("  optimal_value"));
            if (line==null) {
               throw new IOException("No solution was able to be computed.");
            }
            int colon = line.indexOf(':');
            String numSpec = line.substring(colon+1).trim();
            Rational value = Rational.parseRational(numSpec);
            Rational [] point = new Rational[pointValues.size()];
            for (int i=0; i<point.length; i++) {
               point[i] = (Rational)pointValues.get(i);
            }
            solution = new Solution(point,value);
         }
         
         try {
            proc.waitFor();
         } catch (InterruptedException ex) {
            ex.printStackTrace();
            return null;
         }
         proginput.close();
         tmpfile.delete();
         String fname = tmpfile.getName();
         String base = fname.substring(0,fname.lastIndexOf('.'));
         File lps = new File(tmpfile.getParent(),base+".lps");
         lps.delete();
         File ddl = new File(tmpfile.getParent(),base+".ddl");
         ddl.delete();
         return solution;
      }
   }
   
   public static class ComponentFilter extends SubtreeFilter {
      
      Class solverClass;
      LPSolver solver;

      ComponentFilter() {
         super(subtreeNames);
      }
      public void process(Document doc) 
         throws XMLException
      {
         Element top = doc.getDocumentElement();
         String value = top.getText();
         try {
            StringReader sreader = new StringReader(value);
            Solution solution = solver.solve(sreader);
            if (solution==null) {
               throw new XMLException("Program could not be solved.");
            }
            ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
            Element s = constructor.createElement(XML.solutionName);
            s.setAttributeValue("value",solution.getValue().toString());
            post(s);
            post(constructor.createElement(XML.pointName));
            StringBuffer sbuffer = new StringBuffer();
            Rational [] point = solution.getPoint();
            for (int i=0; i<point.length; i++) {
               if (i!=0) {
                  sbuffer.append(' ');
               }
               sbuffer.append(point[i].toString());
            }
            post(constructor.createCharacters(sbuffer.toString()));
            post(constructor.createElementEnd(XML.pointName));
            post(constructor.createElementEnd(XML.solutionName));
         } catch (IOException ex) {
            ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
            XML.error(constructor,output, "Cannot solve program due to: "+ex.getMessage());
         }
      }
   }
   
   /** Creates a new instance of KernelBasisComponent */
   public SolveLPComponent()
   {
      super(componentName);
   }
   
   public Component newInstance() 
      throws XMLException
   {
      final ComponentFilter filter = new ComponentFilter();
      return new ItemFilterComponent(name,vendor,version,filter,inputPortName,outputPortName) 
      {
         public void init(Component.Context context) 
            throws XMLException
         {
            String prop = context.getParameter(InfosetFactory.createName("monos.LPSolver")).toString();
            try {
               if (prop==null) {
                  filter.solverClass = SolveLPComponent.CDDRPlusLPSolver.class;
               } else {
                  filter.solverClass = Class.forName(prop);
               }
               filter.solver = (LPSolver)filter.solverClass.newInstance();
            } catch (Exception ex) {
               throw new XMLException("Cannot load solver class: "+prop,ex);
            }
         }
      };
   }

}
